/*
 *   A task scheduler that implements Monte-Carlo simulation
 *
 *   Copyright (C) 2011 Systasis Computer Systems, Inc.
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *   
 *   $Id: Activity.java 91 2011-08-10 12:28:23Z jchimene@gmail.com $
 */
package com.systasis.sked.client.presenter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import com.google.gwt.view.client.ProvidesKey;
import com.systasis.sked.client.model.ActivityExporter;
import com.systasis.sked.client.model.ActivityImporter;
import com.systasis.sked.client.presenter.Duration.DurationBuilder;

public class Activity implements Comparable<Activity>, ProvidesKey<Activity> {
    public static class ActivityBuilder implements Builder<Activity> {

	private Duration duration;

	private final DurationBuilder durationBuilder;

	private final ArrayList<Edge> edgeList;

	private Integer id;

	private String name;

	private ArrayList<Integer> predecessor;

	private Integer sequenceNumber;

	public ActivityBuilder() {
	    this.durationBuilder = new Duration.DurationBuilder();
	    this.duration = null;
	    this.predecessor = new ArrayList<Integer>();
	    this.edgeList = new ArrayList<Edge>();
	    this.id = 0;
	    this.sequenceNumber = null;
	}

	public ActivityBuilder activityName(String name) {
	    this.name = name;
	    return this;
	}

	public ActivityBuilder best(double val) {
	    durationBuilder.best(val);
	    return this;
	}

	@Override
	public Activity build() {
	    return new Activity(this);
	}

	public ActivityBuilder duration(Duration duration) {
	    this.duration = duration;
	    return this;
	}

	public ActivityBuilder follows(String id) {
	    this.predecessor.add(Integer.valueOf(id));
	    return this;
	}

	public ActivityBuilder id(Integer id) {
	    this.id = id;
	    return this;
	}

	public ActivityBuilder likely(double val) {
	    durationBuilder.likely(val);
	    return this;
	}

	public ActivityBuilder predecessorList(ArrayList<Integer> predecessor) {
	    this.predecessor = predecessor;
	    return this;
	}

	public ActivityBuilder random(double val) {
	    durationBuilder.random(val);
	    return this;
	}

	public ActivityBuilder sequenceNumber(Integer sequenceNumber) {
	    this.sequenceNumber = sequenceNumber;
	    return this;
	}

	public ActivityBuilder worst(double val) {
	    durationBuilder.worst(val);
	    return this;
	}
    }

    public static String EMPTY_ACTIVITY_NAME = "Activity name...";

    /**
     * The key provider that provides the unique ID of an activity.
     * 
     * public static final ProvidesKey<Activity> KEY_PROVIDER = new
     * ProvidesKey<Activity>() {
     * 
     * @Override public Object getKey(Activity activity) { return activity ==
     *           null ? null : activity.getId(); } };
     */

    private static final int EARLIEST = 2;

    private static final int END = 1;

    private static final int LATEST = 3;

    private static int nextId = 1;

    private static final int SIZEOFTIME = 4;

    private static final int START = 0;

    public static <T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c) {
	return asSortedList(c, true);
    }

    public static <T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c, boolean ascending) {
	final List<T> list = new ArrayList<T>(c);
	if (ascending) {
	    Collections.sort(list);
	} else {
	    Collections.sort(list, Collections.reverseOrder());
	}

	return list;

    }

    public static Activity emptyActivity() {
	return new Activity.ActivityBuilder().id(-1).activityName(EMPTY_ACTIVITY_NAME).build();
    }

    public static Activity importerStorage(ActivityImporter.Storage importer) {
	final Duration duration = new Duration.DurationBuilder() //
		.best(importer.provideBest()) //
		.worst(importer.provideWorst()) //
		.likely(importer.provideLikely()) //
		.build();

	final Activity activity = new Activity.ActivityBuilder() //
		.activityName(importer.provideActivityName()) //
		.duration(duration) //
		.id(importer.provideId()) //
		.predecessorList(importer.providePredecessorList()) //
		.build();

	return activity;
    }

    public static void setNextId(int id) {
	nextId = id;
    }

    private String activityName = "";
    private final Duration duration;
    private ArrayList<Edge> edgeList;

    private final int id;

    private final ArrayList<Integer> predecessor;

    private Integer sequenceNumber;

    private final double[] time;

    private Activity(ActivityBuilder activityBuilder) throws IllegalStateException {
	if (null == activityBuilder.name) {
	    throw new IllegalStateException("Missing activity name");
	} else {
	    activityName = activityBuilder.name;
	}

	id = (0 == activityBuilder.id) ? nextId++ : activityBuilder.id;

	time = new double[SIZEOFTIME];
	time[START] = time[END] = time[EARLIEST] = time[LATEST] = 0.0;
	sequenceNumber = activityBuilder.sequenceNumber;
	predecessor = activityBuilder.predecessor;
	duration = (null == activityBuilder.duration) ? activityBuilder.durationBuilder.build() : activityBuilder.duration;
	edgeList = activityBuilder.edgeList;
    }

    @Override
    public int compareTo(Activity t) {
	// ascending order
	return -1 * t.sequenceNumber.compareTo(sequenceNumber);
    }

    public void exporter(ActivityExporter exporter) {
	exporter.addActivityName(activityName.toString());
	exporter.addId(id);
	exporter.addPredecessorList(predecessor);
    }
    
    public void exporterDebug(ActivityExporter.Debug exporter) {
	exporter.addActivityName(activityName.toString());
	exporter.addSequenceNumber(sequenceNumber);
	exporter.addId(id);
	exporter.addPredecessorList(predecessor);
	exporter.addDuration(duration);
	exporter.addEdgeList(edgeList);
	exporter.addEarliestTime(time[EARLIEST]);
	exporter.addLatestTime(time[LATEST]);
    }

    public void exporterStorage(ActivityExporter.Storage exporter) {
	exporter.addActivityName(activityName.toString());
	exporter.addId(id);
	exporter.addPredecessorList(predecessor);
	exporter.addLikely(duration.getLikely());
	exporter.addWorst(duration.getWorst());
	exporter.addBest(duration.getBest());
    }

    public String getActivityName() {
	return activityName;
    }

    public Duration getDuration() {
	return duration;
    }

    public double getDurationBest() {
	return duration.getBest();
    }

    public Double getDurationEstimate() {
	return duration.getEstimate();
    }

    public double getDurationLikely() {
	return duration.getLikely();
    }

    public double getDurationWorst() {
	return duration.getWorst();
    }

    public double getEarliestTime() {
	return time[EARLIEST];
    }

    public ArrayList<Edge> getEdges() {
	return edgeList;
    }

    public Integer getId() {
	return id;
    }

    @Override
    public Object getKey(Activity activity) {
	return activity.sequenceNumber;
    }

    public Double getLatestTime() {
	return time[LATEST];
    }

    public ArrayList<Integer> getPredecessorList() {
	return predecessor;
    }

    public Integer getSequenceNumber() {
	return sequenceNumber;
    }

    public Activity importerDebug(ActivityImporter.Debug importer) {
	final Duration duration = new Duration.DurationBuilder() //
		.duration(importer.provideDuration()).build();

	final Activity activity = new Activity.ActivityBuilder() //
		.activityName(importer.provideActivityName()) //
		.duration(duration) //
		.id(importer.provideId()) //
		.predecessorList(importer.providePredecessorList()) //
		.build();

	activity.setEdgeList(importer.provideEdgeList());
	activity.setEarliestTime(importer.provideEarliestTime());
	activity.setLatestTime(importer.provideLatestTime());
	activity.setSequenceNumber(importer.provideSequenceNumber());

	return activity;
    }

    public void setActivityName(String activityName) {
	this.activityName = activityName;
    }

    public void setDuration(Duration duration) {
	this.duration.setDuration(duration);
    }

    public void setDurationBest(Double best) {
	duration.setBest(best);
    }

    public void setDurationEstimate(double estimate) {
	duration.setEstimate(estimate);
    }

    public void setDurationLikely(Double likely) {
	duration.setLikely(likely);
    }

    public void setDurationWorst(Double worst) {
	duration.setWorst(worst);
    }

    public void setEarliestTime(double earliestTime) {
	time[EARLIEST] = earliestTime;
    }

    public void setEdge(int start, int end) {
	edgeList.add(new Edge(start, end));
    }

    public void setEdgeList(ArrayList<Edge> edgeList) {
	this.edgeList = edgeList;
    }

    public void setLatestTime(double latestTime) {
	time[LATEST] = latestTime;
    }

    public void setPredecessorList(String predecessor) {
	this.predecessor.clear();
	if (0 == predecessor.length()) {
	    return;
	}
	for (final String p : predecessor.split(",")) {
	    this.predecessor.add(Integer.parseInt(p.trim()));
	}
    }

    public void setSequenceNumber(Integer sequenceNumber) {
	this.sequenceNumber = sequenceNumber;
    }
}
